/*
 * $Id: Handshake.java,v 1.9 2009/10/13 04:11:02 afaichun Exp $
 * 
 * Copyright (C) 2008 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.processor.iptds.worker;

import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.ASCUtil;
import com.ge.healthcare.autosc.common.dbLogger.ContactLogger;
import com.ge.healthcare.autosc.common.exceptions.GSCCUtilException;
import com.ge.healthcare.autosc.common.pojo.sysInfo.SystemDetails;
import com.ge.healthcare.autosc.common.util.GSCCUtil;
import com.ge.healthcare.autosc.processor.IPTDSCodeConstants;
import com.ge.healthcare.autosc.processor.exception.IPTDSException;
import com.ge.healthcare.autosc.processor.iptds.IPTDSSocketHandler;
import com.ge.healthcare.autosc.processor.iptds.IPTDSSystemInfo;

/**
 * 
 * @author 212042946
 * 
 */
public class Handshake {

	private static final int REQUEST_MODE_STATE = 0;
	private static final int SERVICE_COMMAND_STATE = 1;
	private static final int SITE_INFO_STATE = 2;
	private static final int SYSTEM_INFO_STATE = 3;
	private static final String[] states = { "REQUEST_MODE_STATE",
			"SERVICE_COMMAND_STATE", "SITE_INFO_STATE", "SYSTEM_INFO_STATE" };

	public Handshake() {
	}

	/**
	 * Start the handshaking
	 * 
	 * @param threadPoolManager
	 *            - Thread pool manager
	 * @param socketHandler
	 * @return IPTDSSystemInfo - remote system information
	 * @throws IPTDSException
	 */
	public IPTDSSystemInfo handShake(IPTDSThreadPoolManager threadPoolManager,
			IPTDSSocketHandler socketHandler) throws IPTDSException {
		int state = REQUEST_MODE_STATE;
		IPTDSSystemInfo systemInfo = null;
		HandshakeResult result = new HandshakeResult();

		try {
			processModeCommand(threadPoolManager, socketHandler, result);
			state = SERVICE_COMMAND_STATE;

			processServiceCommand(socketHandler, result);
			state = SITE_INFO_STATE;

			processSiteInfoCommand(socketHandler, result);
			state = SYSTEM_INFO_STATE;

			systemInfo = getSystemsForIP(result);

			if (systemInfo != null) {
				systemInfo.setHandshakeResult(result);
			}
		} catch (IPTDSException iptdse) {
			StringBuffer msg = new StringBuffer();
			msg.append("Handshaking failure.\n").append("local address: ")
					.append(socketHandler.getLocalAddress().toString()).append(
							"\n").append("local port: ").append(
							Integer.valueOf(socketHandler.getLocalPort())
									.toString()).append("\n").append(
							"Failure state: ").append(states[state]);
			ASCLogger.error(this.getClass(), "handShake", msg.toString(),
					iptdse);
			throw new IPTDSException(msg.toString(), iptdse,
					IPTDSCodeConstants.PRC_F_TDS_003);
		}
		return systemInfo;
	}

	/**
	 * Receive request mode from remote system and determine if thread pool has
	 * available worker to take the request.
	 * 
	 * @param command
	 */
	private void processModeCommand(IPTDSThreadPoolManager threadPoolManager,
			IPTDSSocketHandler socketHandler, HandshakeResult result)
			throws IPTDSException {
		String command = socketHandler.readLine();
		if (command == null) {
			String msg = "No response from the remote client";
			ContactLogger.addContactLog(msg);
			throw new IPTDSException(msg, IPTDSCodeConstants.PRC_F_TDS_000);
		}

		if (!command.equalsIgnoreCase(HandshakeResult.URGENT_MODE)) {
			// check if the communication should take up this worker to work.
			if (threadPoolManager.getAvailableWorker() <= threadPoolManager
					.getUrgentWorkersNum()) {
				String msg = new StringBuffer()
						.append(
								"Non-urgent request while Autosc is currently at peak.\n")
						.append("Remote address: ").append(
								socketHandler.getRemoteAddress())
						.toString();
				ContactLogger.addContactLog(msg);
				socketHandler.sendMessage("access denied - currently at peak");
				throw new IPTDSException(msg, IPTDSCodeConstants.PRC_F_TDS_001);
			}
		}
		result.setMode(command);
	}

	private void processServiceCommand(IPTDSSocketHandler socketHandler,
			HandshakeResult result) throws IPTDSException {
		String dispatchService = "";
		String dispatchServiceArg0 = "";
		String dispatchServiceArg1 = "";
		String serviceCommand = socketHandler.readReplyFor("begin");
		if (serviceCommand == null) {
			String msg = "waiting for repsonse to 'begin'";
			ContactLogger.addContactLog(msg);
			throw new IPTDSException(msg);
		}
		StringTokenizer st = new StringTokenizer(serviceCommand);
		if (st.hasMoreTokens())
			dispatchService = st.nextToken();
		if (st.hasMoreTokens())
			dispatchServiceArg0 = st.nextToken();
		if (st.hasMoreTokens())
			dispatchServiceArg1 = st.nextToken();

		if ((!dispatchService.equalsIgnoreCase(IPTDSClient.getProcessType()))
				|| ((dispatchServiceArg0.indexOf("-F") == -1) && (dispatchServiceArg0
						.indexOf("-f") == -1))
				|| (dispatchServiceArg1.length() < 1)) {
			String msg = new StringBuffer().append(
					"Received invalid dispatch.\n").append("remote address: ")
					.append(socketHandler.getRemoteAddress().toString())
					.append("\n").append("dispatch command: ").append(
							serviceCommand).toString();
			ContactLogger.addContactLog(msg);
			throw new IPTDSException(msg, IPTDSCodeConstants.PRC_F_TDS_002);
		}
		result.setDispatchService(dispatchService);
		if (dispatchServiceArg0.length() > 0) {
			result.addDispatchArg(dispatchServiceArg0);
		}
		if (dispatchServiceArg1.length() > 0) {
			result.addDispatchArg(dispatchServiceArg1);
		}
	}

	private void processSiteInfoCommand(IPTDSSocketHandler socketHandler,
			HandshakeResult result) throws IPTDSException {
		String usn = socketHandler.readLine();
		if (usn == null) {
			String msg = "Failed reading usn from remote system";
			ContactLogger.addContactLog(msg);
			throw new IPTDSException(msg);

		}

		String siteName = socketHandler.readLine();
		/*
		 * NOTE: The site name is not functionally used in the currently Autosc.
		 * To accommodate the future new AutoSC network design, the siteName be
		 * replaced with the client IP address. In the meantime, we are doing
		 * that when the site name is not being used.
		 */
		siteName = socketHandler.getHostAddress();
		if (siteName == null) {
			String msg = "Failed reading siteName from remote system";
			ContactLogger.addContactLog(msg);
			throw new IPTDSException(msg);
		}

		ASCLogger.info(this.getClass(), "processSiteInfoCommand",
				new StringBuffer().append(
						"Remote system claimed itself as USN=").append(usn)
						.append(", Site name=").append(siteName).toString());

		/*
		 * S i m u l a t o r S p e c i a l C a s e
		 * 
		 * For proper system-level actions, i.e., lookup, logging, etc., IPTDS
		 * needs to know what the simulated IP address is -- not the real IP
		 * address of who it is communicating with(which is the simulator!).
		 * Therefore, the simulator protocol will pass in the simulated IP
		 * address thru the USN. So, if the USN has a : in it that signifies it
		 * is coming thru the simulator; therefore, parse everything after the :
		 * as the IP address of the simulated scanner. Readjust scanUSN as
		 * necessary and continue.
		 */
		int colon = usn.indexOf(':');
		if (colon >= 0) {
			result.setSimulator(true);
			String usnip = new String(usn); // USN:IP.Add.re.ss
			usn = usnip.substring(0, colon);
			siteName = usnip.substring(colon + 1);
			ContactLogger.addContactLog("SCANNER SIMULATOR: ip address "
					+ siteName);
		}
		result.setScanUsn(usn);
		result.setScanSiteName(siteName);
	}

	/*
	 * Get a list of system based on the client IP address and USN
	 */
	private IPTDSSystemInfo getSystemsForIP(HandshakeResult result)
			throws IPTDSException {
		SystemDetails validSystem = null;
		ContactLogger.addContactLog("Get system details for "
				+ result.getScanSiteName());
		try {
			Vector<SystemDetails> systemDetailsList = GSCCUtil
					.getSystemDetailsForIPAddress(result.getScanSiteName());
			if(!ASCUtil.isNullOrEmptyList(systemDetailsList)) {
				Iterator<SystemDetails> systemDetailsIter = systemDetailsList
						.iterator();
				while (systemDetailsIter.hasNext()) {
					SystemDetails systemDetail = (SystemDetails) systemDetailsIter
							.next();
					if (result.getScanUsn().equals(systemDetail.getUsn())) {
						validSystem = systemDetail;
						String msg = new StringBuffer().append("System ").append(
								validSystem.getSystemId()).append(
								" is selected for IP address ").append(
								result.getScanSiteName()).toString();
						ContactLogger.addContactLog(validSystem.getSystemId(), msg);
					}
				}
			}
			// If no system matches the criteria, throw an exception
			if (validSystem == null) {
				String msg = new StringBuffer()
						.append(
								"No system matches the IP address and usn combination\n")
						.append("Ip address: ")
						.append(result.getScanSiteName()).append("\n").append(
								"usn: ").append(result.getScanUsn()).append(
								"\n").append(
								"Cannot find the remote client identity.")
						.toString();
				ContactLogger.addContactLog(msg);
				throw new IPTDSException(msg, IPTDSCodeConstants.PRC_F_TDS_005);
			}

			return new IPTDSSystemInfo(validSystem);
		} catch (GSCCUtilException gue) {
			String msg = "Error retrieving system information for client IP address "
					+ result.getScanSiteName();
			ContactLogger.addContactLog(msg);
			throw new IPTDSException(msg, gue, IPTDSCodeConstants.PRC_F_TDS_004);
		}
	}
}
